//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <core.h>
#include <_hal.h>
#include <mantle.h>
#include "common.h"
#include "exception.h"
#include <keyboard.h>
#include "vkey.h"

extern _ELASTOS Boolean g_bDbgStubEntry;

#ifndef _RELEASE

STATIC const char *s_szExceptionDescriptions[] = {
    "Divide Error",
    "Debug",
    "NMI",
    "Breakpoint",
    "Overflow",
    "Bound Range Exceeded",
    "Invalid Opcode",
    "Device Not Available",
    "Double Fault",
    "Coprocessor Segment Overrun",
    "Invalid TSS",
    "Segment Not Present",
    "Stack Fault",
    "General Protection",
    "Page Fault",
    "Intel Reserved",
    "Floating Point Error",
    "Alignment Check",
};

STATIC INLINE const char *GetExceptionDescription(vector_t vector)
{
    assert(vector < sizeof(s_szExceptionDescriptions) / sizeof(const char *));

    return s_szExceptionDescriptions[vector];
}

#endif // _RELEASE

ECode GetDebugCode(struct KBDStatus *pVirtulCode);

EXTERN void CDECL DebuggerHandler(
    vector_t vector,
    InterruptContext *pContext,
    uint_t uPageFaultAddr)
{

#ifndef _RELEASE
    if (!g_bDbgStubEntry) {
        kprintf("\n*EXCEPTION* No.%d %s (cs:eip = %x:%x, error code = %x)\n",
                vector, GetExceptionDescription(vector),
                pContext->cs, pContext->eip, pContext->u.errorcode);
        if (ExceptionVector_PageFault == vector) {
            kprintf("    Page Fault Linear Address = %x\n", uPageFaultAddr);
        }

        kprintf("ds = %x, es = %x, _ss = %x, eflags = %x\n"
                "eax = %x, ebx = %x, ecx = %x, edx  = %x\n"
                "esi = %x, edi = %x, ebp = %x, _esp = %x\n",
                pContext->ds, pContext->es, pContext->_ss, pContext->eflags,
                pContext->eax, pContext->ebx, pContext->ecx, pContext->edx,
                pContext->esi, pContext->edi, pContext->ebp, pContext->_esp);

        CProcess *proc = GetCurrentProcess();
        kprintf("Current Process: %x, Current Thread: %x [%S]\n",
                proc, GetCurrentThread(),
                GetCurrentThread()->m_wszName);

        ModuleView *pModuleView;

        if (proc) {
            ForEachDLinkNode(ModuleView *, pModuleView, &proc->m_moduleList) {
                if (NULL == pModuleView->m_pModule) continue;
                if (FALL_INTO(pContext->eip,
                    pModuleView->m_pModule->m_uImageBase,
                    pModuleView->m_pModule->m_uTotalSize)) {
                    kprintf("Current Module Name: %S (Full Path: %S) \n"
                        " ModuleImageBase: %x \t Offset: %x[ImageBase-PC]\n",
                        pModuleView->m_pModule->m_wszName,
                        pModuleView->m_pModule->m_wszFullPath,
                        pModuleView->m_pModule->m_uImageBase,
                        (pContext->eip - pModuleView->m_pModule->m_uImageBase));
                }
            }

            kprintf("\n");

            InProcessLinkNode *pInProcessLinkNode;
            ForEachDLinkNode(InProcessLinkNode *, pInProcessLinkNode,
                &proc->m_threadList) {
                Thread *pThread =
                    THIS(pInProcessLinkNode, Thread, m_inProcLink);
                if (pThread->m_uUserStackBase) {
                    kprintf("Thread: %x\tThreadUserID: %x\t[%S]:\n",
                        pThread,
                        ((pThread->m_uUserStackBase &
                            ~(USER_STACK_ALIGN - 1)) + 1),
                        pThread->m_wszName);
                }
            }

            kprintf("\n");

            //all module name, baseaddress
            ForEachDLinkNode(ModuleView *, pModuleView, &proc->m_moduleList) {
                if (NULL == pModuleView->m_pModule) continue;
                kprintf("Module: %S (Full Path:%S) ModuleImageBase: %x\n",
                    pModuleView->m_pModule->m_wszName,
                    pModuleView->m_pModule->m_wszFullPath,
                    pModuleView->m_pModule->m_uImageBase);
            }
        }
    }
    else {
        uint32_t dr6 = GetDR6();

        if (dr6 & (DR6_B(0) | DR6_B(1) | DR6_B(2) | DR6_B(3))) {

            if (dr6 & DR6_B(0)) {
                kprintf("Breakpoint 0 in %x\n", GetDR0());
            }

            if (dr6 & DR6_B(1)) {
                kprintf("Breakpoint 1 in %x\n", GetDR1());
            }

            if (dr6 & DR6_B(2)) {
                kprintf("Breakpoint 2 in %x\n", GetDR2());
            }

            if (dr6 & DR6_B(3)) {
                kprintf("Breakpoint 3 in %x\n", GetDR3());
            }
        }

        SetDR6(0);
    }
#endif // _RELEASE

#if (_TEST_TYPE == 2) /* Test for Daily Build */ \
    || (_TEST_TYPE == 7) /* Test for Marshalling */
    int bRebootCmd = FALSE;
    CProcess *pCurrentProcess = GetCurrentProcess();
    if (NULL != pCurrentProcess) {
        WStringBuf_<64> esbName, esbArgs;
        pCurrentProcess->GetStartInfo(&esbName, &esbArgs);
        kprintf("\n\n*********************************************\n\n");
        kprintf("%S %S : ", (wchar_t *)esbName.GetPayload(),
            (wchar_t *)esbArgs.GetPayload());

        if (0 == wcscmp((wchar_t *)esbName.GetPayload(), L"ezregsvr.exe")) {
            bRebootCmd = TRUE;
        }
    }

    if (IS_IN_KERNEL_MODE(pContext)) {
        kprintf("Kernel debugger!\n");
    }
    else {
        kprintf("User debugger!\n");
        if (bRebootCmd) {
            kprintf("\nTesting-System: Rebooting...\n");
            BspReboot();
        }

        if (NULL != pCurrentProcess) {
            kprintf("\nTesting-System: Terminate current process...\n");
            kprintf("\n*********************************************\n\n");
            pCurrentProcess->Kill();
        }
    }

    kprintf("\nTesting-System: Rebooting...\n");
    BspReboot();
    return;
#endif //_TEST_TYPE == 2 || _TEST_TYPE == 7

    if (!g_bDbgStubEntry) {
        if (IS_IN_KERNEL_MODE(pContext)) {
            kprintf("\nKernel debugger!");
        }
        else {
            kprintf("\nUser debugger!");
        }
    }

#ifndef _RELEASE
    if (!g_bDbgStubEntry) {
        KBDStatus recieveInfo;
        kprintf("\nProgram Fault. Debug Or Terminate[D/T]?");
        while (TRUE) {
            GetDebugCode(&recieveInfo);
            if (recieveInfo.uVirKey == VK_D
                && recieveInfo.uFlags == KEYEVENTFLAG_KEYDOWN) {
                break;
            }
            else if (recieveInfo.uVirKey == VK_T
                && recieveInfo.uFlags == KEYEVENTFLAG_KEYDOWN) {
                CProcess *pCurrentProcess = GetCurrentProcess();
                if (NULL != pCurrentProcess) {
                    kprintf("\nTerminate current process.\n");
                    pCurrentProcess->Kill();
                }
                else {
                    kprintf("\nSystem Rebooting ...\n");
                    BspReboot();
                }
            }
            else {
                if (recieveInfo.uFlags == KEYEVENTFLAG_KEYDOWN)
                    kprintf("\nProgram Fault. Debug Or Terminate[D/T]?");
            }
        }
    }
#endif //_RELEASE

    // Todo: optimize
    if (IS_IN_KERNEL_MODE(pContext)) {
        if (!g_bDbgStubEntry) kprintf("\nWait for remote debugger.\n");
        uint32_t uFlags = SaveFlagsAndCli();
        KdbHandler(vector, pContext);
        RestoreIF(uFlags);
    }
    else {
        if (!g_bDbgStubEntry) kprintf("\nWait for remote debugger.\n");
        uint32_t uFlags = SaveFlagsAndCli();
        UdbHandler(vector, pContext);
        RestoreIF(uFlags);
    }
}
